﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace ExtractFanIn
{
    /// <summary>
    /// Author: Aaron Lumaksana
    /// Date: 16/08/10
    /// This class contains a sorted dictionary of nodes. 
    /// It has methods that reads a text file to populate the dictionary.
    /// It also has methods to output the fan-in text file.
    /// </summary>
    class NodeCollection
    {
        private SortedDictionary<string, Node> _nodes = new SortedDictionary<string, Node>();
        private const int _fanOutCountParam = 2;
        private const int _type = 0;
        private const int _name = 1;

        private double _elapsed;

        public int numofnodes()
        {
            return _nodes.Values.Count;
        }

        public double Elapsed
        {
            get { return _elapsed; }
        }
        private int _lineCount;

        public int LineCount
        {
            get { return _lineCount; }
        }

        private int _numberOfSource;
        public int NumberOfSource
        {
            get
            {
                _numberOfSource = 0;
                foreach (Node n in _nodes.Values)
                {
                    if (n.FanInCount == 0)
                    {
                        _numberOfSource++;
                    }
                }
                return _numberOfSource;
            }
        }

        private int _numberOfSink;
        public int NumberOfSink
        {
            get
            {
                _numberOfSink = 0;
                foreach (Node n in _nodes.Values)
                {
                    if (n.FanOutCount == 0)
                    {
                        _numberOfSink++;
                    }
                }
                return _numberOfSink;
            }
        }

        private int _maxFanOut;
        public int MaxFanOut
        {
            get
            {
                _maxFanOut = 0;
                
                foreach (Node n in _nodes.Values)
                {
                    int lFanOutcount = n.FanOutCount;
                    if (lFanOutcount > _maxFanOut)
                    {
                        _maxFanOut = lFanOutcount;
                    }
                }
                return _maxFanOut;
            }
        }

        private int _maxFanIn;
        public int MaxFanIn
        {
            get
            {
                _maxFanIn = 0;
                foreach (Node n in _nodes.Values)
                {
                    if (n.FanInCount > _maxFanIn)
                    {
                        _maxFanIn = n.FanInCount;
                    }
                }
                return _maxFanIn;
            }
        }
        
        #region CheckLines
        //Checks that the number of nodes in the line = the claimed number in the line.
        private bool _checkLastParam(string aline)
        {
            string[] lParam = aline.Split(',');
            int lNumberofFanOutNodes = int.Parse(lParam[_fanOutCountParam]);

            // -3 because the first three parameters are type,name,fanoutcout.
            return (lParam.Length - 3) == lNumberofFanOutNodes;
        }

        private bool _lineValid(string aLine)
        {
            // bounding followed by I,A or C followed by comma followed by a-Z and period multiple times followed
            // by comma followed by 0-9 multipletimes followed by newline or return char or comma. 
            if (Regex.IsMatch(aLine, "^[IAC][,][a-zA-Z.0-9_-]*[,][0-9]*[,a-zA-Z.0-9_-]*$"))
            {
                return _checkLastParam(aLine);
            }
            else
            {
                return false;
            }
        }

        private void _checkLine(string aLine)
        {
            if (!_lineValid(aLine))
            {
                throw new Exception("File corrupted at : " + aLine);
            }
        }
        #endregion

        #region Populate
        /// <summary>
        /// ehecks if a node exists
        /// </summary>
        /// <param name="aName">name of node</param>
        /// <param name="aExistingNode">out parameter if node exists it will out the node. </param>
        /// <returns>Boolean</returns>
        private bool _nodeExists(string aName, out Node aExistingNode)
        {
            return _nodes.TryGetValue(aName, out aExistingNode);

        }

        private void _addNode(Node aNode, string[] aParams)
        {

            int lFanOutCount = int.Parse(aParams[_fanOutCountParam]);


            if (lFanOutCount != 0)
            {
                _addChild(aParams, aNode);
            }
            
            _nodes.Add(aParams[_name], aNode);
            
        }
        /// <summary>
        /// creates parent/child relation
        /// </summary>
        /// <param name="aParent">parent node</param>
        /// <param name="aChild">child node</param>
        private void _createRelation(Node aParent, Node aChild)
        {

            aParent.AddFanOut(aChild);
            aChild.AddFanIn(aParent);

        }

        private void _addChild(string[] aParams, Node aNode)
        {
            const int fanOutParam = 3;
            int lNumberOfChilds = aParams.Length;

            for (int i = fanOutParam; i < lNumberOfChilds; i++)
            {
                string lName = aParams[i];
                Node lNode = new Node(lName);
                Node lExistingNode;
                if (!_nodeExists(lName, out lExistingNode))
                {
                    _nodes.Add(lName, lNode);
                    _nodes.TryGetValue(lName, out lExistingNode);
                }
                _createRelation(aNode, lExistingNode);

                
            }
        }
        /// <summary>
        /// input missing to existing nodes
        /// </summary>
        /// <param name="aExistingNode">the existing node</param>
        /// <param name="aParams">the data in string array</param>
        private void _processExistingNode(Node aExistingNode, string[] aParams)
        {
            
            int lFanOutCount = int.Parse(aParams[_fanOutCountParam]);

            if (lFanOutCount == 0)
            {
                return;
            }
            _addChild(aParams, aExistingNode);
        }

        private void _processNode(string[] aParams)
        {
            

            Node lNewNode;

            if (_nodeExists(aParams[_name], out lNewNode))
            {
                lNewNode.ClassType = aParams[_type][0];
                _processExistingNode(lNewNode, aParams);
            }
            else
            {
                lNewNode = new Node(aParams[_name]);
                lNewNode.ClassType = aParams[_type][0];
                _addNode(lNewNode, aParams);
            }


        }

        private void _processLine(string aLine)
        {
            string[] lParams = aLine.Split(',');
            _processNode(lParams);

        }

        public void Populate(string aFile)
        {
            Stopwatch lStopWatch = new Stopwatch();


            _lineCount = 0;
            
            if (File.Exists(aFile))
            {
                using (StreamReader lReader = new StreamReader(aFile))
                {
                    string lLine;
                    lStopWatch.Start();
                    while ((lLine = lReader.ReadLine()) != null)
                    {
                        _checkLine(lLine);
                        _processLine(lLine);
                        _lineCount++;
                    }

                    lStopWatch.Stop();
                    _elapsed = lStopWatch.Elapsed.TotalMilliseconds;
                    
                }
            }
            else
            {
                throw new Exception("Filename '" + aFile + "' was not found.");
            }
        }
        #endregion

        #region outputfanin
        private string _nodeToLine(Node aN)
        {
            int lFanInCount = aN.FanInCount;

            StringBuilder builder = new StringBuilder();
            builder.Append(aN.ClassType);
            builder.Append("," + aN.Name + "," + lFanInCount);
            foreach (Node n in aN.FanIn)
            {
                builder.Append("," + n.Name);
            }
            return builder.ToString();


        }
        public void outputFanIn(string aFile)
        {
            Stopwatch lStopWatch = new Stopwatch();
            string line;
            lStopWatch.Start();
         
            using (StreamWriter lWriter = new StreamWriter(aFile))
            {
                
                foreach (Node n in _nodes.Values)
                {
                    line = _nodeToLine(n);
                    lWriter.WriteLine(line);
                }
            }
            lStopWatch.Stop();
            _elapsed = lStopWatch.Elapsed.TotalMilliseconds;
            

        }
        #endregion

    }
}
